package com.hwtx.soa.registry.path;

import com.google.common.collect.Maps;

import com.hwtx.soa.registry.ZkClient;
import com.hwtx.soa.registry.util.ConfigUtils;
import com.hzgj.bcl.soa.config.ReferenceDef;
import com.hzgj.bcl.soa.config.ServerDef;
import com.hzgj.bcl.soa.config.ServiceDef;
import com.hzgj.bcl.util.lang.StrKit;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import static com.hwtx.soa.registry.Constants.APPLICATION_KEY;
import static com.hwtx.soa.registry.Constants.CATEGORY;
import static com.hwtx.soa.registry.Constants.CATEGORY_KEY;
import static com.hwtx.soa.registry.Constants.CHECK_KEY;
import static com.hwtx.soa.registry.Constants.CONSUMER;
import static com.hwtx.soa.registry.Constants.CONSUMERS_CATEGORY;
import static com.hwtx.soa.registry.Constants.DUBBO_VERSION_KEY;
import static com.hwtx.soa.registry.Constants.GENERIC_KEY;
import static com.hwtx.soa.registry.Constants.GROUP_KEY;
import static com.hwtx.soa.registry.Constants.INTERFACE_KEY;
import static com.hwtx.soa.registry.Constants.METHODS_KEY;
import static com.hwtx.soa.registry.Constants.PATH_SEPARATOR;
import static com.hwtx.soa.registry.Constants.PID_KEY;
import static com.hwtx.soa.registry.Constants.PROVIDERS_CATEGORY;
import static com.hwtx.soa.registry.Constants.PROVIDER_SIDE;
import static com.hwtx.soa.registry.Constants.REVISION_KEY;
import static com.hwtx.soa.registry.Constants.ROOT;
import static com.hwtx.soa.registry.Constants.SIDE_KEY;
import static com.hwtx.soa.registry.Constants.TIMESTAMP_KEY;
import static com.hwtx.soa.registry.Constants.VERSION_KEY;
import static com.hzgj.bcl.soa.registry.RegistryConstants.SERVICE_DOWN;

public class DubboPath implements Path {

    private static Logger logger = LoggerFactory.getLogger(DubboPath.class);
    public static String version;

    static {
        Properties prop = new Properties();
        InputStream s = null;
        try {
            s = DubboPath.class.getResourceAsStream("/META-INF/dubbo.properties");
            prop.load(s);
        } catch (IOException e) {

        } finally {
            if (s != null) {
                try {
                    s.close();
                } catch (IOException ex) {
                }
            }
        }
        version = prop.getProperty("version");
    }

    @Override
    public String getProviderPath(ServiceDef.Attr attr) {
        return getProviderPrefix(attr.getInterface()) + PATH_SEPARATOR + ConfigUtils.encode(getProviderUrl(attr));
    }

    @Override
    public String getProviderPrefix(String service) {
        return ROOT + PATH_SEPARATOR + service + PATH_SEPARATOR + PROVIDERS_CATEGORY;
    }

    private String getConsumerPrefix(String service) {
        return ROOT + PATH_SEPARATOR + service + PATH_SEPARATOR + CONSUMERS_CATEGORY;
    }

    @Override
    public String getName() {
        return CATEGORY;
    }

    @Override
    public String getDownChildByServiceAndIpPort(ZkClient client, String service, String ipAndPort) {
        List<String> children = client.getChildren(getDownPathPrefix(service));
        for (String child : children) {
            String _path = ConfigUtils.decode(child);
            if (_path.indexOf(ipAndPort) > 0) {
                return child;
            }
        }
        return null;
    }

    @Override
    public String getConsumerChildByServiceAndIp(ZkClient client, String service, String ip) {
        List<String> children = client.getChildren(getConsumerPrefix(service));
        for (String child : children) {
            String _path = ConfigUtils.decode(child);
            if (_path.indexOf(ip) > 0) {
                return child;
            }
        }
        return null;
    }

    @Override
    public String getProviderChildByServiceAndIpPort(ZkClient client, String service, String ipAndPort) {
        List<String> children = client.getChildren(getProviderPrefix(service));
        for (String child : children) {
            String _path = ConfigUtils.decode(child);
            if (_path.indexOf(ipAndPort) > 0) {
                return child;
            }
        }
        return null;
    }

    private String getDownPathPrefix(String service) {
        return ROOT + PATH_SEPARATOR + SERVICE_DOWN + PATH_SEPARATOR + service + PATH_SEPARATOR + PROVIDERS_CATEGORY;
    }

    @Override
    public String getDownPath(String service, String child) {
        return getDownPathPrefix(service) + PATH_SEPARATOR + child;
    }

    @Override
    public String getUpPath(String service, String child) {
        return getProviderPrefix(service) + PATH_SEPARATOR + child;
    }

    //consumer://172.29.65.41/com.bkjk.platform.uc.api.org.EhrOrgFacade?application=fn-workflow&
    // category=consumers&check=false&default.timeout=5000&dubbo=2.8.4&
    // interface=com.bkjk.platform.uc.api.org.EhrOrgFacade&
    // methods=getEhrOrgByOrgCode,getEhrOrgByParentOrgCode,queryEhrOrgListByLimit,getLjOrgByOrgCode,getLJAllTreeData,getBKAllTreeData
    // &pid=28069&revision=0.3-SNAPSHOT&side=consumer&timestamp=1515403964402
    @Override
    public String getConsumerPath(ReferenceDef referenceDef) {
        return getConsumerPrefix(referenceDef.getService()) + PATH_SEPARATOR + ConfigUtils.encode(getConsumerUrl(referenceDef));
    }

    @Override
    public String getConsumerPath(String service, String child) {
        return getConsumerPrefix(service) + PATH_SEPARATOR + child;
    }

    private String getConsumerUrl(ReferenceDef referenceDef) {
        Map<String, Object> map = Maps.newTreeMap();
        map.put(SIDE_KEY, PROVIDER_SIDE);
        map.put(CATEGORY_KEY, CONSUMERS_CATEGORY);
        map.put(CHECK_KEY, referenceDef.isCheck());
        map.put(DUBBO_VERSION_KEY, version);
        map.put(TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
        if (ConfigUtils.getPid() > 0) {
            map.put(PID_KEY, String.valueOf(ConfigUtils.getPid()));
        }
        if (StrKit.notBlank(referenceDef.getVersion())) {
            map.put(VERSION_KEY, referenceDef.getVersion());
        }

        if (StrKit.notBlank(referenceDef.getGroup())) {
            map.put(GROUP_KEY, referenceDef.getGroup());
        }

        map.put(GENERIC_KEY, false);
        map.put(INTERFACE_KEY, referenceDef.getService());
        fillMethodsAndRevision(map, referenceDef.getInterfaceClass());
        return CONSUMER + "://" + referenceDef.getHost() + PATH_SEPARATOR + referenceDef.getService() + "?" + getMapAsString(map);

    }

    // dubbo://10.4.100.2:20880/com.bkjk.platform.uc.api.org.EhrOrgFacade?anyhost=true&
    // application=platform-uc-api&default.timeout=5000&dubbo=2.8.4
    // &generic=false&interface=com.bkjk.platform.uc.api.org.EhrOrgFacade
    // &logger=slf4j&methods=getEhrOrgByOrgCode,getEhrOrgByParentOrgCode,queryEhrOrgListByLimit,getLjOrgByOrgCode,getBKAllTreeData,getLJAllTreeData
    // &owner=zhanjiang.feng&pid=66
    // &revision=0.2-SNAPSHOT&side=provider&timestamp=1515405746226

    private String getProviderUrl(ServiceDef.Attr attr) {
        Map<String, Object> map = Maps.newTreeMap();
        map.put(SIDE_KEY, PROVIDER_SIDE);
        map.put(DUBBO_VERSION_KEY, version);
        map.put(TIMESTAMP_KEY, String.valueOf(System.currentTimeMillis()));
        if (ConfigUtils.getPid() > 0) {
            map.put(PID_KEY, String.valueOf(ConfigUtils.getPid()));
        }
        map.put(APPLICATION_KEY, attr.getApplication());
//        map.put(BIND_IP_KEY, attr.getServerDef().getHost());
//        map.put(BIND_PORT_KEY, attr.getServerDef().getPort());
        map.put(GENERIC_KEY, false);
        map.put(INTERFACE_KEY, attr.getInterface());
        if (StrKit.notBlank(attr.getVersion())) {
            map.put(VERSION_KEY, attr.getVersion());
        }

        if (StrKit.notBlank(attr.getGroup())) {
            map.put(GROUP_KEY, attr.getGroup());
        }

        fillMethodsAndRevision(map, attr.getInterfaceClass());

        return CATEGORY + "://" + attr.getServerDef().getHost() + ":" + attr.getServerDef().getPort() + PATH_SEPARATOR
                + attr.getInterface() + "?" + getMapAsString(map);
    }

    private void fillMethodsAndRevision(Map<String, Object> map, String serviceClass) {
        StringBuilder builder = new StringBuilder();
        try {
            Class<?> clazz = Thread.currentThread().getContextClassLoader().loadClass(serviceClass);
            map.put(REVISION_KEY, ConfigUtils.getVersion(clazz, "0.1"));
            for (Method method : clazz.getMethods()) {
                builder.append(method.getName()).append(",");
            }
        } catch (ClassNotFoundException e) {
            logger.error("{}", e);
        }
        map.put(METHODS_KEY, builder.substring(0, builder.length() - 2));
    }

    private String getMapAsString(Map<String, Object> map) {
        StringBuilder builder = new StringBuilder();
        map.forEach((k, v) -> {
            builder.append(k).append("=").append(v.toString().trim());
        });
        return builder.substring(0, builder.length() - 2);
    }

    @Override
    public ServiceDef.Attr buildService(String url) {

        ServiceDef.Attr attrBuilder = new ServiceDef.Attr();
        url = ConfigUtils.decode(url);

        int i = url.indexOf("?"); // seperator between body and parameters
        if (i >= 0) {
            String[] parts = url.substring(i + 1).split("\\&");
            for (String part : parts) {
                part = part.trim();
                if (part.length() > 0) {
                    int j = part.indexOf('=');
                    String name, value;
                    if (j >= 0) {
                        name = part.substring(0, j);
                        value = part.substring(j + 1);

                    } else {
                        name = value = part;
                    }

                    if (name.equals(GROUP_KEY)) {
                        attrBuilder.setGroup(value);
                    } else if (name.equals(VERSION_KEY)) {
                        attrBuilder.setVersion(value);
                    } else if (name.equals(TIMESTAMP_KEY)) {
                        attrBuilder.setTime(new Date(Long.parseLong(value)));
                    }

                }
            }
            url = url.substring(0, i);
        }

        i = url.indexOf("://");
        if (i >= 0) {
            if (i == 0) {
                throw new IllegalStateException("url missing protocol: \"" + url + "\"");
            }
            url = url.substring(i + 3);
        } else {
            // case: file:/path/to/file.txt
            i = url.indexOf(":/");
            if (i >= 0) {
                if (i == 0) {
                    throw new IllegalStateException("url missing protocol: \"" + url + "\"");
                }
                url = url.substring(i + 1);
            }
        }

        i = url.indexOf("/");
        if (i >= 0) {
            url = url.substring(0, i);
        }
        ServerDef serverDef = new ServerDef("");
        i = url.indexOf(":");
        if (i >= 0 && i < url.length() - 1) {
            serverDef.setPort(Integer.parseInt(url.substring(i + 1)));
            url = url.substring(0, i);
        }
        if (url.length() > 0) {
            serverDef.setHost(url);
        }
        attrBuilder.setServerDef(serverDef);
        return attrBuilder;
    }
}
